﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

namespace WCF_AutoUpdates_Services.WCFService
{
    /// <summary>
    /// 自动升级
    /// </summary>
    public class AutoUpdate : IAutoUpdate
    {
        /// <summary>
        /// 自动更新系统配置文件
        /// </summary>
        const string CONST_CONFIGFILE_NAME = "AutoUpdate.xml";

        /// <summary>
        /// 自动更新项目信息文件
        /// </summary>
        const string CONST_UPDATECONFIG_NAME = "Update.xml";
        /// <summary>
        /// 验证密码
        /// </summary>
        const string CONST_PASSWORD = "WinAhriman";

        /// <summary>
        /// 自动更新消息
        /// </summary>
        public static event Action<string, bool> Event_OnMessage;

        /// <summary>
        /// 获取项目版本信息
        /// </summary>
        /// <param name="ProjectName">项目名称</param>
        /// <param name="Version">版本号</param>
        /// <param name="VerDate">版本发布日期</param>
        /// <returns></returns>
        public bool GetProjectVersion(string ProjectName, out string Version, out string VerDate)
        {
            Version = VerDate = "";

            string updatePathString = GetProjectPath(ProjectName);

            if (updatePathString == "") return false;

            updatePathString = string.Format(@"{0}\{1}\{2}", System.Environment.CurrentDirectory, updatePathString, CONST_UPDATECONFIG_NAME);

            if (!System.IO.File.Exists(updatePathString)) return false;

            try
            {
                XDocument xDoc = XDocument.Load(updatePathString);

                VerDate = xDoc.Descendants("UpdateTime").First().Attribute("Date").Value;

                Version = xDoc.Descendants("Version").First().Attribute("Num").Value;

                return true;

            }
            catch (Exception e)
            {
                if (Event_OnMessage != null) Event_OnMessage(string.Format("【{0}】 获取项目版本信息时出现错误，{1}...", DateTime.Now, e.Message), true);

                return false;
            }
        }

        /// <summary>
        /// 获取项目名称列表
        /// </summary>
        /// <returns></returns>
        public string[] GetPrjNames()
        {
            string path = string.Format(@"{0}\{1}", System.Environment.CurrentDirectory, CONST_CONFIGFILE_NAME);

            if (!System.IO.File.Exists(path)) return new string[0];

            try
            {
                XDocument xDoc = XDocument.Load(path);

                IEnumerable<string> enumStrings = from x in xDoc.Descendants("Project")
                                                  select x.Attribute("Name").Value;

                if (Event_OnMessage != null) Event_OnMessage(string.Format("【{0}】 获取项目列表完成...", DateTime.Now), true);

                return enumStrings.ToArray();
                
            }
            catch (Exception e)
            {
                if (Event_OnMessage != null) Event_OnMessage(string.Format("【{0}】 获取项目列表时出现错误，{1}...", DateTime.Now, e.Message), true);
                return new string[0];
            }
        }

        /// <summary>
        /// 获取项目路径(相对路径)
        /// </summary>
        /// <param name="ProjectName">项目名称</param>
        /// <returns></returns>
        public string GetProjectPath(string ProjectName)
        {
            string path = "";

            string autoUpdatePath = string.Format(@"{0}\{1}", System.Environment.CurrentDirectory, CONST_CONFIGFILE_NAME);

            try
            {
                XDocument xDoc = XDocument.Load(autoUpdatePath);

                path = xDoc.Descendants("Project").Where((xe) => xe.Attribute("Name").Value == ProjectName).First().Element("Value").Value;

                return path;
            }
            catch (Exception e)
            {
                if (Event_OnMessage != null) Event_OnMessage(string.Format("【{0}】 获取项目路径出现一个意外错误：{1}...", DateTime.Now, e.Message), true);
                return "";
            }
        }

        /// <summary>
        /// 获取文件列表
        /// </summary>
        /// <param name="ProjectName">项目名称</param>
        /// <returns></returns>
        public FileDataInfo[] GetFileQuantity(string ProjectName)
        {
            string path = GetProjectPath(ProjectName);
            if (path == "") return new FileDataInfo[0];
            path = string.Format(@"{0}\{1}", System.Environment.CurrentDirectory, path);

            if (!System.IO.Directory.Exists(path))
            {
                if (Event_OnMessage != null) Event_OnMessage(string.Format("【{0}】 项目：{1} 路径：{2}，未找到", DateTime.Now, ProjectName, path), true);
                return new FileDataInfo[0];
            }

            string[] prjfilesPath = System.IO.Directory.GetFiles(path);

            List<FileDataInfo> fileItems = new List<FileDataInfo>();

            getFileList(new System.IO.DirectoryInfo(path), ref fileItems, path);

            if (Event_OnMessage != null) Event_OnMessage(string.Format("【{0}】 项目：{1} 获取文件列表成功，文件数量：{2}", DateTime.Now, ProjectName, fileItems.Count), false);

            return fileItems.ToArray();
        }

        /// <summary>
        /// 下载文件
        /// </summary>
        /// <param name="ProjectName">项目名称</param>
        /// <param name="DataInfo">文件数据对象</param>
        /// <returns></returns>
        public FileDataInfo DownLoadFile(string ProjectName, FileDataInfo DataInfo)
        {
            if (DataInfo.Offset == 0)
            {
                string updatePath = GetProjectPath(ProjectName);
                if (updatePath == "") return null;
                updatePath = string.Format(@"{0}\{1}", System.Environment.CurrentDirectory, updatePath);

                if (!System.IO.Directory.Exists(updatePath))
                {
                    if (Event_OnMessage != null) Event_OnMessage(string.Format("【{0}】项目路径:{1}不存在...", DateTime.Now, updatePath), true);
                    return null;
                }

                string filepath = string.Format(@"{0}\{1}", updatePath, DataInfo.Name);

                if (!System.IO.File.Exists(filepath))
                {
                    if (Event_OnMessage != null) Event_OnMessage(string.Format("【{0}】项目:{1} 中文件：{2}不存在...", DateTime.Now, ProjectName, filepath), true);
                    return null;
                }
                DataInfo.Name = filepath;
            }
            using (System.IO.FileStream fs = new System.IO.FileStream(DataInfo.Name, System.IO.FileMode.Open))
            {
                DataInfo.Data = new byte[fs.Length - DataInfo.Offset <= DataInfo.BuffLen ? fs.Length - DataInfo.Offset : DataInfo.BuffLen];

                fs.Position = DataInfo.Offset;

                fs.Read(DataInfo.Data, 0, DataInfo.Data.Length);

                DataInfo.Length = fs.Length;

                if (fs.Position == fs.Length)
                {
                    if (Event_OnMessage != null)
                    {
                        Event_OnMessage(string.Format("【{0} 客户端下载文件：{1} 完成...", DateTime.Now, DataInfo.Name), false);
                    }
                }
            }

            return DataInfo;
        }

        /// <summary>
        /// 设置服务器项目版本
        /// </summary>
        /// <param name="Version">版本号</param>
        /// <param name="VerDate">版本发布日期</param>
        /// <param name="PrjName">项目名称</param>
        /// <param name="Password">密码</param>
        /// <returns></returns>
        public bool SetProjectVersion(string Version, string VerDate, string PrjName, string Password)
        {
            if (!Password.Equals(CONST_PASSWORD))
            {
                if (Event_OnMessage != null) Event_OnMessage(string.Format("【{0} 客户端设置服务器版本密码验证失败...", DateTime.Now), true);
                return false;
            }

            string prjPath = GetProjectPath(PrjName);

            if (prjPath == "") return false;

            string updateConfigPath = string.Format(@"{0}\{1}", prjPath, CONST_UPDATECONFIG_NAME);

            XDocument xDoc = new XDocument(new XElement("AutoUpdater", new XElement("UpdateTime", new XAttribute("Date", VerDate)), new XElement("Version", new XAttribute("Num", Version))));

            xDoc.Save(updateConfigPath);

            if (Event_OnMessage != null)
            {
                Event_OnMessage(string.Format("【{0} 客户端设置项目：{1} 版本信息完成...", DateTime.Now, PrjName), false);
            }

            return true;

        }

        /// <summary>
        ///  删除项目
        /// </summary>
        /// <param name="ProjectName">项目名称</param>
        /// <param name="PasswordString">密码</param>
        /// <returns></returns>
        public bool DeleteProject(string ProjectName, string PasswordString)
        {
            if (PasswordString != CONST_PASSWORD)
            {
                if (Event_OnMessage != null) Event_OnMessage(string.Format("【{0} 客户端删除项目：{1} 密码验证失败...", DateTime.Now, ProjectName), true);
                return false;
            }
            if (!ProjectFolderClear(ProjectName)) return false;

            string configPath = string.Format(@"{0}\{1}", System.Environment.CurrentDirectory, CONST_CONFIGFILE_NAME);

            if (!System.IO.File.Exists(configPath)) return true;

            try
            {
                XDocument xDoc = XDocument.Load(configPath);

                xDoc.Descendants("Project").Where((xe) => xe.Attribute("Name").Value == ProjectName).First().Remove();

                xDoc.Save(configPath);

            }
            catch (Exception e)
            {
                if (Event_OnMessage != null) Event_OnMessage(string.Format("【{0}】 删除项目配置信息出现一个意外错误：{1}...", DateTime.Now, e.Message), true);
                return false;
            }
            return true;
        }

        /// <summary>
        /// 设置项目的名称和路径
        /// </summary>
        /// <param name="ProjectName">项目名称</param>
        /// <param name="ProjectDir">项目路径（相对）</param>
        /// <param name="PasswordString">密码</param>
        /// <returns></returns>
        public bool SetProject(string ProjectName, string ProjectDir, string PasswordString)
        {
            if (PasswordString != CONST_PASSWORD)
            {
                if (Event_OnMessage != null) Event_OnMessage(string.Format("【{0} 客户端设置项目：{1} 密码验证失败...", DateTime.Now, ProjectName), true);
                return false;
            }

            if (ProjectDir[0] == '\\' || ProjectDir[0] == '/') ProjectDir = ProjectDir.Substring(1);

            if (ProjectDir[ProjectDir.Length - 1] == '\\' || ProjectDir[ProjectDir.Length - 1] == '/') ProjectDir = ProjectDir.Substring(0, ProjectDir.Length - 1);

            string configfilePath = string.Format(@"{0}\{1}", System.Environment.CurrentDirectory, CONST_CONFIGFILE_NAME);

            XDocument xDoc = new XDocument();

            if (!System.IO.File.Exists(configfilePath))
            {
                xDoc.Add(new XElement("AutoUpdater"));
            }
            else
            {
                try
                {
                    xDoc = XDocument.Load(configfilePath);
                }
                catch
                {
                    xDoc.Add(new XElement("AutoUpdater"));
                }
            }

            var xel = from x in xDoc.Descendants("Project")
                      where x.Attribute("Name").Value == ProjectName
                      select x;

            if (xel.Count() != 0)
            {
                XElement element = xel.First();
                element.Element("Value").Value = "";
                element.Element("Value").Add(new XCData(ProjectDir));
            }
            else
            {
                XElement element = new XElement("Project", new XAttribute("Name", ProjectName), new XElement("Value", new XCData(ProjectDir)));
                xDoc.Element("AutoUpdater").Add(element);
            }
            xDoc.Save(configfilePath);

            if (Event_OnMessage != null) Event_OnMessage(string.Format("【{0} 客户端设置项目：{1} 配置信息完成...", DateTime.Now, ProjectName), false);

            return true;

        }

        /// <summary>
        /// 项目文件夹清理
        /// </summary>
        /// <param name="PrjName">项目名称</param>
        /// <returns></returns>
        public bool ProjectFolderClear(string PrjName)
        {
            string prjpath = GetProjectPath(PrjName);

            if (prjpath == "") return false;

            prjpath = string.Format(@"{0}\{1}", System.Environment.CurrentDirectory, prjpath);

            if (!System.IO.Directory.Exists(prjpath)) return true;

            try
            {
                System.IO.Directory.Delete(prjpath, true);
            }
            catch (Exception e)
            {
                if (Event_OnMessage != null) Event_OnMessage(string.Format("【{0}】 清除项目文件出现一个意外错误：{1}...", DateTime.Now, e.Message), true);
                return false;
            }
            return true;
        }

        /// <summary>
        /// 项目文件上传
        /// </summary>
        /// <param name="PrjName">项目名称</param>
        /// <param name="DataInfo">项目文件数据对象</param>
        /// <param name="Password">密码</param>
        /// <returns></returns>
        public FileDataInfo UpdateFile(string PrjName, FileDataInfo DataInfo, string Password)
        {
            if (DataInfo.Offset == 0)
            {
                if (Password != CONST_PASSWORD)
                {
                    if (Event_OnMessage != null) Event_OnMessage(string.Format("【{0} 客户端上传项目：{1} 文件时，密码验证失败...", DateTime.Now,DataInfo.Name), true);
                    return null;
                }

                string filePath = GetProjectPath(PrjName);

                if (filePath == "" || DataInfo.Name == "") return null;

                if (DataInfo.Name[0] == '\\' || DataInfo.Name[0] == '/') DataInfo.Name = DataInfo.Name.Substring(1);

                filePath = string.Format(@"{0}\{1}\{2}", System.Environment.CurrentDirectory, filePath, DataInfo.Name);

                if (System.IO.File.Exists(filePath))
                {
                    if (DataInfo.MD5.Equals(getFileMD5(filePath)))
                    {
                        DataInfo.Offset = DataInfo.Length;
                        DataInfo.Data = null;
                        return DataInfo;
                    }
                    else
                    {
                        System.IO.File.Delete(filePath);
                    }
                }
                else
                {
                    if (!CreateFileDir(filePath)) return null;
                }
                DataInfo.Name = filePath;
            }
            else
            {
                if (!System.IO.File.Exists(DataInfo.Name)) return null;
            }

            using (System.IO.FileStream fs = new System.IO.FileStream(DataInfo.Name, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write))
            {
                fs.Seek(DataInfo.Offset, System.IO.SeekOrigin.Begin);

                fs.Write(DataInfo.Data,0,DataInfo.Data.Length);

                DataInfo.Offset = fs.Length;
                DataInfo.Data = null;

                fs.Close();
            }

            if (DataInfo.Offset == DataInfo.Length)
            {
                if (Event_OnMessage != null)  Event_OnMessage(string.Format("【{0} 客户端上传文件：{1} 完成...", DateTime.Now, DataInfo.Name), false);
            }

            return DataInfo;
        }


        /// <summary>
        /// 创建文件父域文件夹
        /// </summary>
        /// <param name="filePath">文件绝对路径</param>
        /// <returns></returns>
        private static bool CreateFileDir(string filePath)
        {
            string[] tmparr = filePath.Split('\\');

            if (tmparr.Length == 0) return false;

            string dirPath = tmparr[0];

            try
            {
                for (int i = 1; i < tmparr.Length - 1; i++)
                {
                    dirPath += "\\" + tmparr[i];

                    if (!System.IO.Directory.Exists(dirPath))
                    {
                        System.IO.Directory.CreateDirectory(dirPath);
                    }
                }
            }
            catch (Exception e)
            {
                if (Event_OnMessage != null) Event_OnMessage(string.Format("【{0}】 创建项目文件文件夹出现一个意外错误：{1}...", DateTime.Now, e.Message), true);
                return false;
            }
            return true;
        }

        /// <summary>
        /// 获取文件列表
        /// </summary>
        /// <param name="dir">文件夹对象</param>
        /// <param name="items">文件数据对象列表</param>
        /// <param name="replaceString">需要替换的文本（绝对路径变相对路径）</param>
        private static void getFileList(System.IO.DirectoryInfo dir, ref List<FileDataInfo> items, string replaceString)
        {
            foreach (System.IO.FileSystemInfo f in dir.GetFileSystemInfos())
            {
                if (f is System.IO.FileInfo)
                {
                    System.IO.FileInfo fi = f as System.IO.FileInfo;

                    FileDataInfo item = new FileDataInfo() { Name = fi.FullName.Replace(replaceString, "") };

                    if (item.Name[0] == '\\') item.Name = item.Name.Substring(1);

                    item.MD5 = getFileMD5(fi.FullName);

                    items.Add(item);
                }
                else
                {
                    System.IO.DirectoryInfo d = f as System.IO.DirectoryInfo;
                    if (d == null) continue;
                    getFileList(d, ref items, replaceString);
                }
            }
        }

        /// <summary>
        /// 获取文件MD5效验码
        /// </summary>
        /// <param name="filepath"></param>
        /// <returns></returns>
        private static string getFileMD5(string filepath)
        {
            System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();

            string md5CheckCode = "";

            using (System.IO.FileStream fs = new System.IO.FileStream(filepath, System.IO.FileMode.Open))
            {
                md5CheckCode = BitConverter.ToString(md5.ComputeHash(fs)).Replace("-", "");
            }
            return md5CheckCode;
        }

    }
}
